using System;
using System.Linq;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
 
namespace Linquid
{		
	/// <summary>
	/// Used to define order of the layout.
	/// </summary>
	public class LayoutDirection 
	{
		/// <summary>
		/// Place controls horizontally starting from left.
		/// </summary>
		public static readonly LayoutDirection FromLeft = 
			new LayoutDirection(layouts => new RowLayout(layouts.ToArray()));
		/// <summary>
		/// Place controls horizontally starting from right.
		/// </summary>
		public static readonly LayoutDirection FromRight =
			new LayoutDirection(layouts => new RowLayout(layouts.Reverse().ToArray()));
		/// <summary>
		/// Place controls vertically starting from top.
		/// </summary>
		public static readonly LayoutDirection FromTop =
			new LayoutDirection(layouts => new ColumnLayout(layouts.ToArray()));
		/// <summary>
		/// Place controls vertically starting from bottom.
		/// </summary>
		public static readonly LayoutDirection FromBottom =
			new LayoutDirection(layouts => new ColumnLayout(layouts.Reverse().ToArray()));

		/// <summary>
		/// Generate layout that places controls into a table with row major order.
		/// </summary>
		/// <param name="columns">Number of columns in the table.</param>
		/// <returns>Layout to use with UI action.</returns>
		public static LayoutDirection RowMajorTable(int columns)
		{
			return new LayoutDirection(layouts => new RowMajorTableLayout(columns, layouts.ToArray()));
		}
		
		/// <summary>
		/// Generate layout that places controls into a table with column major order.
		/// </summary>
		/// <param name="rows">Number of rows in the table.</param>
		/// <returns>Layout to use with UI action.</returns>
		public static LayoutDirection ColumnMajorTable(int rows)
		{
			return new LayoutDirection(layouts => new ColumnMajorTableLayout(rows, layouts.ToArray()));
		}
		
		private Func<IEnumerable<Layout>, Layout> F {get; set;}
		
		private LayoutDirection(Func<IEnumerable<Layout>, Layout> f)
		{
			F = f;
		}
		
		internal Layout Apply(IEnumerable<Layout> layouts)
		{
			return F(layouts);
		}
	};
	
	internal class StretchPriority
	{
		public static readonly StretchPriority Fixed = new StretchPriority(0, 0);
		
		public float Horizontal {get; private set;}
		public float Vertical {get; private set;}		
		
		public StretchPriority(float priority): this(priority, priority){}
		
		public StretchPriority(float horizontal, float vertical)
		{
			Horizontal = horizontal;
			Vertical = vertical;
		}		
	}
	
	internal abstract class Layout
	{		
		public static readonly int XMargin = 3;
		public static readonly int YMargin = 3;
		
		public abstract void ApplyTo(Control container, Rectangle target);
		public abstract Size MinRequiredSpace {get;}
				
		public abstract StretchPriority StretchPriority {get;}		
		
		public static void Apply(Layout layout, Control container)
		{
			Action apply = delegate 
			{
				var target = container.ClientRectangle;
				var marginTarget = new Rectangle(target.Left + XMargin, target.Top + YMargin, target.Width - XMargin * 2, target.Height - YMargin * 2);
				layout.ApplyTo(container, 
				               marginTarget);								
			};			
			var size = layout.MinRequiredSpace;
			container.ClientSize = new Size(size.Width + XMargin * 2, size.Height + YMargin * 2);
			apply();
			container.Resize += delegate {apply();};
			
		}
	}
	
	internal class AtomicLayout: Layout
	{
		private Control control;
		private Size minSize;
		private StretchPriority priority;
		
		public AtomicLayout(Control control, Size minSize, StretchPriority priority)
		{
			this.control = control;
			this.minSize = minSize;			
			this.priority = priority;
		}
		
		public AtomicLayout(Control control, Size minSize):
			this(control, minSize, StretchPriority.Fixed) {}
				
		public AtomicLayout(Control control): this(control, control.Size){}
					
		public override Size MinRequiredSpace 
		{
			get {return minSize;}			
		}
					
		public override StretchPriority StretchPriority
		{
			get {return priority;}
		} 
		
		public override void ApplyTo(Control container, Rectangle target)
		{
			if (control != null)
			{
				if (!container.Controls.Contains(control))
				{
					container.Controls.Add(control);
				}

				control.Bounds = target;					
			}
		}
	}
		
	internal abstract class TableLayout: Layout
	{		
		protected Layout[][] Columns {get; set;}
		protected Layout[][] Rows {get; set;}
		
		public override void ApplyTo(Control container, Rectangle target)
		{											
			var widths = 
				(from column in Columns
			     select column.Max(item => item.MinRequiredSpace.Width)).ToArray();						
			
			var heights = 
				(from row in Rows
				 select row.Max(item => item.MinRequiredSpace.Height)).ToArray();
			
			var widthPriorities = 
				StretchPriority.Horizontal > 0 ?
					(from column in Columns					
				 	 select column.Max(item => item.StretchPriority.Horizontal 
				     	/ StretchPriority.Horizontal)).ToArray() :
					new float[Columns.Length];
			
			var heightPriorities = 
				StretchPriority.Vertical > 0 ?
					(from row in Rows
				 	 select row.Max(item => 
				     	item.StretchPriority.Vertical / StretchPriority.Vertical)).ToArray() :
					new float[Rows.Length];
			
			var excessWidth = target.Width - MinRequiredSpace.Width;
			var excessHeight = target.Height - MinRequiredSpace.Height;
												
			var y = 0;
			foreach (var j in Enumerable.Range(0, Rows.Length))
			{
				var x = 0;
				foreach (var i in Enumerable.Range(0, Rows[j].Length))
				{				
					var subTarget = new Rectangle(target.X + x, target.Y + y, 
					                              widths[i] + (int) (widthPriorities[i] * excessWidth),
				                              	  heights[j] + (int) (heightPriorities[j] * excessHeight));
					Rows[j][i].ApplyTo(container, subTarget);
					x += widths[i] + (int) (widthPriorities[i] * excessWidth);					
				}
				y += heights[j] + (int) (heightPriorities[j] * excessHeight);
			}
		}	
		
		public override Size MinRequiredSpace
		{
			get {return
					new Size(
						(from row in Rows						
						 select row.Sum(item => item.MinRequiredSpace.Width)).Max(),
						(from column in Columns
						 select column.Sum(item => item.MinRequiredSpace.Height)).Max());}
		}
			
		public override StretchPriority StretchPriority
		{
			get {return 
					new StretchPriority(
						(from row in Rows						
						 select row.Sum(item => item.StretchPriority.Horizontal)).Max(),
						(from column in Columns
						 select column.Sum(item => item.StretchPriority.Vertical)).Max());}
		}
	}
	
	internal class RowMajorTableLayout: TableLayout
	{
		public RowMajorTableLayout(int columns, params Layout[] layouts)
		{									
			Columns =
				(from i in Enumerable.Range(0, layouts.Length)				
				 group layouts[i] by i % columns).
				Select(g => g.ToArray()).ToArray();
			
			Rows =
				(from i in Enumerable.Range(0, (int) Math.Ceiling(layouts.Length / (float) columns))
				 select (from column in Columns
				   	     where column.Length > i
				   	     select column[i]).ToArray()).ToArray();
		}		
	}
	
	internal class ColumnMajorTableLayout: TableLayout
	{
		public ColumnMajorTableLayout(int rows, params Layout[] layouts)
		{									
			Rows =
				(from i in Enumerable.Range(0, layouts.Length)				
				 group layouts[i] by i % rows).
				Select(g => g.ToArray()).ToArray();
			
			Columns =
				(from i in Enumerable.Range(0, (int) Math.Ceiling(layouts.Length / (float) rows))
				 select (from row in Rows
				   	     where row.Length > i
				   	     select row[i]).ToArray()).ToArray();
		}		
	}
	
	internal class ColumnLayout: Layout
	{		
		protected Layout[] Layouts {get; private set;}
						
		public ColumnLayout(params Layout[] layouts)
		{
			Layouts = layouts;
		}
		
		public override void ApplyTo(Control container, Rectangle target)
		{															
			var width = 0;			
			var heights = (int[]) null;
			if (MinRequiredSpace.Width >= target.Width)
			{
				width = MinRequiredSpace.Width;
			}
			else
			{
				width = target.Width;
			}
			
			heights = Layouts.Select(l => l.MinRequiredSpace.Height).ToArray();
						
			var excess = target.Height - MinRequiredSpace.Height;
			
			var heightPriorities = (float[]) null;
			if (StretchPriority.Vertical > 0)
			{
				heightPriorities = Layouts.Select(layout => layout.StretchPriority.Vertical / StretchPriority.Vertical).ToArray();
			}
			else
			{
				heightPriorities = new float[Layouts.Length];
			}
			
			var y = 0;
			foreach (var i in Enumerable.Range(0, Layouts.Length))
			{				
				var subTarget = new Rectangle(target.X, target.Y + y, 
				                              width, 
				                              heights[i] + (int) (heightPriorities[i] * excess));
				Layouts[i].ApplyTo(container, subTarget);
				y += heights[i] + (int) (heightPriorities[i] * excess);
			}		
		}	
		
		public override Size MinRequiredSpace
		{
			get {return
					new Size(
						Layouts.Max(
							layout => layout.MinRequiredSpace.Width),
						Layouts.Sum(
							layout => layout.MinRequiredSpace.Height));}
		}
			
		public override StretchPriority StretchPriority
		{
			get {return 
					new StretchPriority(
						Layouts.Max(
							layout => layout.StretchPriority.Horizontal),
						Layouts.Sum(
							layout => layout.StretchPriority.Vertical));}
		}
	}
		
	internal class RowLayout: Layout
	{				
		protected Layout[] Layouts {get; private set;}
				
		public RowLayout(params Layout[] layouts)
		{
			Layouts = layouts;
		}
		
		public override void ApplyTo(Control container, Rectangle target)
		{								
			var widths = (int[]) null;			
			var height = 0;

			widths = Layouts.Select(l => l.MinRequiredSpace.Width).ToArray();
						
			if (MinRequiredSpace.Height >= target.Height)
			{
				height = MinRequiredSpace.Height;
			}
			else
			{
				height = target.Height;
			}
			
			var excess = target.Width - MinRequiredSpace.Width;
			
			
			var widthPriorities = (float[]) null;
			if (StretchPriority.Horizontal != 0)
			{
				widthPriorities = Layouts.Select(layout => layout.StretchPriority.Horizontal / StretchPriority.Horizontal).ToArray();
			}
			else
			{
				widthPriorities = new float[Layouts.Length];
			}
				
			var x = 0;
			foreach (var i in Enumerable.Range(0, Layouts.Length))
			{
				var subTarget = new Rectangle(target.X + x, target.Y, 
				                              widths[i] + (int) (widthPriorities[i] * excess), 
				                              height);
				Layouts[i].ApplyTo(container, subTarget);
				x += widths[i] + (int) (widthPriorities[i] * excess);
			}						
		}	
						
		public override Size MinRequiredSpace
		{
			get {return 
					new Size(
						Layouts.Sum(
							layout => layout.MinRequiredSpace.Width),
						Layouts.Max(
							layout => layout.MinRequiredSpace.Height));}
		}
		
		public override StretchPriority StretchPriority
		{
			get {return 
					new StretchPriority(
						Layouts.Sum(
							layout => layout.StretchPriority.Horizontal),
						Layouts.Max(
							layout => layout.StretchPriority.Vertical));}
		}
	}
		
	internal class TitleLayout: Layout
	{				
		private string title;
		private Layout layout;
		private GroupBox box;
		
		public TitleLayout(string title, Font font, Layout layout)
		{
			this.title = title;
			this.layout = layout;
			
			box = new GroupBox() 
			{
				Text = title,
				Font = font
			};			
		}
		
		private int LeftMargin
		{
			get {return (box.ClientSize.Width - box.DisplayRectangle.Width);}
		}
		
		private int RightMargin
		{
			get {return LeftMargin;}
		}
		
		private int TopMargin
		{
			get {return box.ClientSize.Height - box.DisplayRectangle.Height - 3 * BottomMargin / 4;}			
		}
		
		private int BottomMargin
		{
			get {return LeftMargin;}
		}
		
		public override void ApplyTo(Control container, Rectangle target)
		{			
			if (!container.Controls.Contains(box))
			{
				container.Controls.Add(box);
			}			
			box.Bounds = target;				
			var rectangle = new Rectangle(LeftMargin, 
			                              TopMargin,
			                              box.Size.Width - (LeftMargin + RightMargin),
			                              box.Size.Height - (TopMargin + BottomMargin));
			layout.ApplyTo(box, rectangle);			
		}
		
		public override Size MinRequiredSpace 
		{
			get 
			{
				var space = layout.MinRequiredSpace;
				return new Size(
					space.Width + (LeftMargin + RightMargin),
					space.Height + (TopMargin + BottomMargin));
			}
		}
		
		public override StretchPriority StretchPriority
		{
			get {return layout.StretchPriority;}
		}
	}	
}